package com.blah.gwtgames.client.mines;

import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.SourcesChangeEvents;
import com.google.gwt.user.client.ui.ChangeListenerCollection;
import java.util.Iterator;
import java.util.Set;

public class Grid extends Composite implements SourcesChangeEvents {
	private ChangeListenerCollection changeListenerCollection;
	private VerticalPanel minePanel = new VerticalPanel();
	private Tile[][] tiles;
	private int width;
	private int height;
	private int mineCount;
	private int tilesRemaining;
	private int flagCount;
	private int gameState;
	
	public static final int NEWGAME = 1;
	public static final int PLAYING = 2;
	public static final int FINISHED = 3;
	
	public Grid ()
	{
		Tile.prefetchImages();
		
		initWidget(minePanel);
	}
	
	public Grid (int width, int height, int mineCount)
	{
		Tile.prefetchImages();
		
		setSize(width, height, mineCount);
				
		initWidget(minePanel);
	}
	
	public void setSize(int width, int height, int mineCount)
	{
		this.width = width;
		this.height = height;
		this.mineCount = mineCount;
		
		Iterator iterator = minePanel.iterator();
		while (iterator.hasNext())
		{
			iterator.next();
			iterator.remove();
		}
		
		tiles = new Tile[width][height];
		for (int y = 0; y < height; ++y)
		{
			HorizontalPanel hPanel = new HorizontalPanel();
			for (int x = 0; x < width; ++x)
			{
				Tile tile = new Tile(this, x, y);
				hPanel.add(tile);
				tiles[x][y] = tile;
			}
			minePanel.add(hPanel);
		}
		
		reset();
	}
	
	public void reset()
	{
		gameState = NEWGAME;
		
		tilesRemaining = width * height - mineCount;
		flagCount = 0;
		for (int x = 0; x < width; x++)
		{
			for (int y = 0; y < height; y++)
			{
				tiles[x][y].reset();
			}
		}
		
		fireChange();
	}
	
	public void newGame(Tile firstTile)
	{
		gameState = PLAYING;
				
		int minesPlaced = 0;
		while (minesPlaced < mineCount)
		{
			int x = (int)(Math.random() * width);
			int y = (int)(Math.random() * height);
			
			if (!tiles[x][y].isMined() && tiles[x][y] != firstTile)
			{
				tiles[x][y].placeMine();
				minesPlaced++;
			}
		}
	}
	
	public void endGame(boolean won)
	{
		gameState = FINISHED;
		
		for (int x = 0; x < width; x++)
		{
			for (int y = 0; y < height; y++)
			{
				if (won && !tiles[x][y].isFlagged())
					tiles[x][y].toggleFlag();
				
				tiles[x][y].updateImage();
			}
		}
	}
	
	public int getWidth()
	{
		return width;
	}
	
	public int getHeight()
	{
		return height;
	}
	
	public int getMineCount()
	{
		return mineCount;
	}
	
	public int getTilesRemaining()
	{
		return tilesRemaining;
	}
	
	public int getFlagCount()
	{
		return flagCount;
	}
	
	public Tile getTile(int x, int y)
	{
		return tiles[x][y];
	}
	
	public int getGameState()
	{
		return gameState;
	}
	
	public void onClick(Tile tile)
	{
		if (gameState == FINISHED)
			return;
		
		if (gameState == NEWGAME)
			newGame(tile);
		
		if (tile.isCovered())
		{
			if (tile.isFlagged())
				return;
			
			int tilesCleared = tile.uncover();
			if (tilesCleared == -1)
			{
				endGame(false);
			}
			else
			{
				tilesRemaining -= tilesCleared;
				if (tilesRemaining == 0)
					endGame(true);
			}		
		}
		else
		{
			Set adjacentTiles = tile.adjacentTiles();
			Iterator iterator = adjacentTiles.iterator();
			int adjacentFlags = 0;
			while (iterator.hasNext())
			{
				if (((Tile)iterator.next()).isFlagged())
					adjacentFlags++;
			}
			
			if (adjacentFlags == tile.adjacentMines())
			{
				iterator = adjacentTiles.iterator();
				while (iterator.hasNext())
				{
					Tile adjacentTile = (Tile)iterator.next();
					if (adjacentTile.isCovered() && !adjacentTile.isFlagged())
					{
						int tilesCleared = adjacentTile.uncover();
						if (tilesCleared == -1)
						{
							endGame(false);
						}
						else
						{
							tilesRemaining -= tilesCleared;
							if (tilesRemaining == 0)
								endGame(true);
						}
					}
				}
			}
		}
		
		fireChange();
	}
	
	public void onSpecialClick(Tile tile)
	{
		if (gameState == FINISHED)
			return;
		
		if (tile.isCovered())
		{
			tile.toggleFlag();
			
			if (tile.isFlagged())
				flagCount++;
			else
				flagCount--;
		}
		
		fireChange();
	}

	public void addChangeListener(ChangeListener listener) {
		if (changeListenerCollection == null)
			changeListenerCollection = new ChangeListenerCollection();
		
		changeListenerCollection.add(listener);
	}

	public void removeChangeListener(ChangeListener listener) {
		if (changeListenerCollection != null)
			changeListenerCollection.remove(listener);
	}
	
	private void fireChange() {
		if (changeListenerCollection != null)
			changeListenerCollection.fireChange(this);
	}
}
